package cn.lingyangwl.agile.infra.weixin.mp.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.lingyangwl.agile.infra.weixin.mp.assembly.MpAutoReplyAssembly;
import cn.lingyangwl.agile.infra.weixin.mp.constants.MpAutoReplyTypeEnum;
import cn.lingyangwl.agile.infra.weixin.mp.manager.MpAutoReplyManager;
import cn.lingyangwl.agile.infra.weixin.mp.mapper.MpAutoReplyMapper;
import cn.lingyangwl.agile.infra.weixin.mp.model.entity.MpAutoReply;
import cn.lingyangwl.agile.infra.weixin.mp.model.rqrs.MpAutoReplyPageReq;
import cn.lingyangwl.agile.infra.weixin.mp.model.rqrs.MpAutoReplySaveReq;
import cn.lingyangwl.agile.infra.weixin.mp.model.rqrs.MpAutoReplyUpdateReq;
import cn.lingyangwl.agile.infra.weixin.mp.model.rqrs.MpMessageSendOutReq;
import cn.lingyangwl.agile.infra.weixin.mp.handler.WxMpHandler;
import cn.lingyangwl.agile.infra.weixin.mp.service.MpAutoReplyService;
import cn.lingyangwl.agile.infra.weixin.mp.service.MpMessageService;
import cn.lingyangwl.agile.infra.weixin.mp.utils.MpUtils;
import cn.lingyangwl.framework.mybatis.plus.utils.PageUtil;
import cn.lingyangwl.framework.tool.core.exception.Assert;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.validation.Validator;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

/**
 * 公众号消息自动回复
 *
 * @author shenguangyang
 */
@Slf4j
@Service
public class MpAutoReplyServiceImpl extends ServiceImpl<MpAutoReplyMapper, MpAutoReply>
    implements IService<MpAutoReply>, MpAutoReplyService {

    @Resource
    private MpAutoReplyManager mpAutoReplyManager;
    @Resource
    private MpAutoReplyAssembly mpAutoReplyAssembly;
    @Resource
    private Validator validator;
    @Resource
    private MpMessageService mpMessageService;
    @Resource
    @Lazy
    private WxMpHandler mpProcess;
    
    /**
     * 分页查询 公众号消息自动回复
     *
     * @return 返回分页数据
     */
    @Override
    public IPage<MpAutoReply> pageAutoReply(MpAutoReplyPageReq req) {
        LocalDateTime beginTime = req.getBeginLocalDateTime();
        LocalDateTime endTime = req.getEndLocalDateTime();
        LambdaQueryWrapper<MpAutoReply> queryWrapper = MpAutoReply.lqw()
            .eq(Objects.nonNull(req.getType()), MpAutoReply::getType, req.getType())
            .ge(Objects.nonNull(beginTime), MpAutoReply::getCreatedAt, beginTime)
            .le(Objects.nonNull(endTime), MpAutoReply::getCreatedAt, endTime)
            .orderByDesc(MpAutoReply::getCreatedAt);
        return this.page(PageUtil.to(req), queryWrapper);
    }
    
    /**
     * 查询 公众号消息自动回复 所有记录
     *
     * @return 返回集合，没有返回空List
     */
    @Override
    public List<MpAutoReply> listAutoReply() {
    	return this.list();
    }


    /**
     * 根据主键查询 公众号消息自动回复
     *
     * @param id 主键
     * @return 返回记录，没有返回null
     */
    @Override
    public MpAutoReply getAutoReplyById(Long id) {
        return this.getById(id);
    }

    /**
     * 保存 公众号消息自动回复
     *
     * @param req 新增的记录
     * @return 返回影响行数
     */
    @Override
    public boolean saveAutoReply(MpAutoReplySaveReq req) {
        // 1. 校验数据
        if (req.getResponseMessageType() != null) {
            MpUtils.validateMessage(validator, req.getResponseMessageType(), req);
        }
        validateAutoReplyConflict(null, req.getType(),
                req.getRequestKeyword(), req.getRequestMessageType());

        // 2. 插入数据
        String appId = mpProcess.getContext().getMpParam().getAppId();
        MpAutoReply autoReply = mpAutoReplyAssembly.toAutoReply(req).setMpAppId(appId);
    	return this.save(autoReply);
    }


    /**
     * 更新 公众号消息自动回复
     *
     * @param req 修改的记录
     * @return 返回影响行数
     */
    @Override
    public boolean updateAutoReplyById(MpAutoReplyUpdateReq req) {
        // 1. 校验数据
        if (req.getResponseMessageType() != null) {
            MpUtils.validateMessage(validator, req.getResponseMessageType(), req);
        }

        MpAutoReply autoReply = this.getById(req.getId());
        Assert.notNull(autoReply, new BizException("自动回复不存在"));

        validateAutoReplyConflict(req.getId(), req.getType(), req.getRequestKeyword(), req.getRequestMessageType());

        // 2. 更新数据
        MpAutoReply updateObj = mpAutoReplyAssembly.toAutoReply(req)
                .setMpAppId(null); // 避免前端传递，更新着两个字段
    	return this.updateById(updateObj);
    }


    /**
     * 校验自动回复是否冲突
     *
     * 不同的 type，会有不同的逻辑：
     * 1. type = SUBSCRIBE 时，不允许有其他的自动回复
     * 2. type = MESSAGE 时，校验 requestMessageType 已经存在自动回复
     * 3. type = KEYWORD 时，校验 keyword 已经存在自动回复
     *
     * @param id 自动回复编号
     * @param type 类型
     * @param requestKeyword 请求关键词
     * @param requestMessageType 请求消息类型
     */
    private void validateAutoReplyConflict(Long id, Integer type,
                                           String requestKeyword, String requestMessageType) {
        // 获得已经存在的自动回复
        MpAutoReply autoReply = null;
        String error = null;
        if (MpAutoReplyTypeEnum.SUBSCRIBE.getType().equals(type)) {
            autoReply = mpAutoReplyManager.getAutoReply(MpAutoReplyTypeEnum.SUBSCRIBE);
            error = "已存在关注时的回复";
        } else if (MpAutoReplyTypeEnum.MESSAGE.getType().equals(type)) {
            autoReply = mpAutoReplyManager.getAutoReply(requestMessageType);
            error = "已存在该消息类型的回复";
        } else if (MpAutoReplyTypeEnum.KEYWORD.getType().equals(type)) {
            autoReply = mpAutoReplyManager.getAutoReplyByAccountIdAndKeyword(requestKeyword);
            error = "已关在该关键字的回复";
        }
        if (autoReply == null) {
            return;
        }

        // 存在冲突，抛出业务异常
        if (id == null // 情况一，新增（id == null），存在记录，说明冲突
                || ObjectUtil.notEqual(id, autoReply.getId())) { // 情况二，修改（id != null），id 不匹配，说明冲突
            throw new BizException(error);
        }
    }

    /**
     * 通过id删除 公众号消息自动回复
     *
     * @param id 删除的主键
     * @return 返回影响行数
     */
    @Override
    public boolean deleteAutoReplyById(Long id) {
    	return this.removeById(id);
    }
    
    /**
     * 通过ids删除 公众号消息自动回复
     *
     * @param ids 删除的主键
     * @return 返回影响行数
     */
    @Override
    public boolean deleteAutoReplyByIds(List<Long> ids) {
    	return this.removeByIds(ids);
    }

    @Override
    public WxMpXmlOutMessage replyForSubscribe(String mpAppId, WxMpXmlMessage wxMessage) {
        // 1. 匹配自动回复
        List<MpAutoReply> replies = mpAutoReplyManager.listByMpAppId(mpAppId, MpAutoReplyTypeEnum.SUBSCRIBE);
        MpAutoReply reply = CollUtil.isNotEmpty(replies) ? CollUtil.getFirst(replies)
                : buildDefaultSubscribeAutoReply(mpAppId); // 如果不存在，提供一个默认末班

        // 2. 基于自动回复，创建消息
        MpMessageSendOutReq req = mpAutoReplyAssembly.toMpMessageSendOutRQ(wxMessage.getFromUser(), reply);
        return mpMessageService.sendOutMessage(req);
    }

    private MpAutoReply buildDefaultSubscribeAutoReply(String wxAppId) {
        // 构建默认的【关注】自动回复
        return new MpAutoReply().setMpAppId(wxAppId)
                .setType(MpAutoReplyTypeEnum.SUBSCRIBE.getType())
                .setResponseMessageType(WxConsts.XmlMsgType.TEXT).setResponseContent("感谢关注");
    }
}
